/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- *//* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: *//* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. *//* Code to start and animate CSS transitions. */#include"nsTransitionManager.h"#include"nsAnimationManager.h"#include"mozilla/dom/CSSTransitionBinding.h"#include"nsIContent.h"#include"nsContentUtils.h"#include"nsStyleContext.h"#include"mozilla/MemoryReporting.h"#include"mozilla/TimeStamp.h"#include"nsRefreshDriver.h"#include"nsRuleProcessorData.h"#include"nsRuleWalker.h"#include"nsCSSPropertyIDSet.h"#include"mozilla/EffectCompositor.h"#include"mozilla/EffectSet.h"#include"mozilla/EventDispatcher.h"#include"mozilla/ServoBindings.h"#include"mozilla/StyleAnimationValue.h"#include"mozilla/dom/DocumentTimeline.h"#include"mozilla/dom/Element.h"#include"nsIFrame.h"#include"Layers.h"#include"FrameLayerBuilder.h"#include"nsCSSProps.h"#include"nsCSSPseudoElements.h"#include"nsDisplayList.h"#include"nsStyleChangeList.h"#include"nsStyleSet.h"#include"mozilla/RestyleManager.h"#include"mozilla/RestyleManagerInlines.h"#include"nsDOMMutationObserver.h"usingmozilla::TimeStamp;usingmozilla::TimeDuration;usingmozilla::dom::Animation;usingmozilla::dom::AnimationPlayState;usingmozilla::dom::CSSTransition;usingmozilla::dom::KeyframeEffectReadOnly;usingnamespacemozilla;usingnamespacemozilla::css;namespace{structTransitionEventParams{EventMessagemMessage;StickyTimeDurationmElapsedTime;TimeStampmTimeStamp;};}// anonymous namespacedoubleElementPropertyTransition::CurrentValuePortion()const{MOZ_ASSERT(!GetLocalTime().IsNull(),"Getting the value portion of an animation that's not being ""sampled");// Transitions use a fill mode of 'backwards' so GetComputedTiming will// never return a null time progress due to being *before* the animation// interval. However, it might be possible that we're behind on flushing// causing us to get called *after* the animation interval. So, just in// case, we override the fill mode to 'both' to ensure the progress// is never null.TimingParamstimingToUse=SpecifiedTiming();timingToUse.SetFill(dom::FillMode::Both);ComputedTimingcomputedTiming=GetComputedTiming(&timingToUse);MOZ_ASSERT(!computedTiming.mProgress.IsNull(),"Got a null progress for a fill mode of 'both'");MOZ_ASSERT(mKeyframes.Length()==2,"Should have two animation keyframes for a transition");returnComputedTimingFunction::GetPortion(mKeyframes[0].mTimingFunction,computedTiming.mProgress.Value(),computedTiming.mBeforeFlag);}voidElementPropertyTransition::UpdateStartValueFromReplacedTransition(){if(!mReplacedTransition){return;}MOZ_ASSERT(nsCSSProps::PropHasFlags(TransitionProperty(),CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR),"The transition property should be able to be run on the ""compositor");MOZ_ASSERT(mTarget&&mTarget->mElement->OwnerDoc(),"We should have a valid document at this moment");dom::DocumentTimeline*timeline=mTarget->mElement->OwnerDoc()->Timeline();ComputedTimingcomputedTiming=GetComputedTimingAt(dom::CSSTransition::GetCurrentTimeAt(*timeline,TimeStamp::Now(),mReplacedTransition->mStartTime,mReplacedTransition->mPlaybackRate),mReplacedTransition->mTiming,mReplacedTransition->mPlaybackRate);if(!computedTiming.mProgress.IsNull()){doublevaluePosition=ComputedTimingFunction::GetPortion(mReplacedTransition->mTimingFunction,computedTiming.mProgress.Value(),computedTiming.mBeforeFlag);MOZ_ASSERT(mProperties.Length()==1&&mProperties[0].mSegments.Length()==1,"The transition should have one property and one segment");MOZ_ASSERT(mKeyframes.Length()==2,"Transitions should have exactly two animation keyframes");MOZ_ASSERT(mKeyframes[0].mPropertyValues.Length()==1,"Transitions should have exactly one property in their first ""frame");constAnimationValue&replacedFrom=mReplacedTransition->mFromValue;constAnimationValue&replacedTo=mReplacedTransition->mToValue;AnimationValuestartValue;if(mDocument->IsStyledByServo()){startValue.mServo=Servo_AnimationValues_Interpolate(replacedFrom.mServo,replacedTo.mServo,valuePosition).Consume();if(startValue.mServo){mKeyframes[0].mPropertyValues[0].mServoDeclarationBlock=Servo_AnimationValue_Uncompute(startValue.mServo).Consume();mProperties[0].mSegments[0].mFromValue=Move(startValue);}}elseif(StyleAnimationValue::Interpolate(mProperties[0].mProperty,replacedFrom.mGecko,replacedTo.mGecko,valuePosition,startValue.mGecko)){nsCSSValuecssValue;DebugOnly<bool>uncomputeResult=StyleAnimationValue::UncomputeValue(mProperties[0].mProperty,startValue.mGecko,cssValue);MOZ_ASSERT(uncomputeResult,"UncomputeValue should not fail");mKeyframes[0].mPropertyValues[0].mValue=cssValue;mProperties[0].mSegments[0].mFromValue=Move(startValue);}}mReplacedTransition.reset();}////////////////////////// CSSTransition ////////////////////////////JSObject*CSSTransition::WrapObject(JSContext*aCx,JS::Handle<JSObject*>aGivenProto){returndom::CSSTransitionBinding::Wrap(aCx,this,aGivenProto);}voidCSSTransition::GetTransitionProperty(nsString&aRetVal)const{MOZ_ASSERT(eCSSProperty_UNKNOWN!=mTransitionProperty,"Transition Property should be initialized");aRetVal=NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(mTransitionProperty));}AnimationPlayStateCSSTransition::PlayStateFromJS()const{FlushStyle();returnAnimation::PlayStateFromJS();}voidCSSTransition::PlayFromJS(ErrorResult&aRv){FlushStyle();Animation::PlayFromJS(aRv);}voidCSSTransition::UpdateTiming(SeekFlagaSeekFlag,SyncNotifyFlagaSyncNotifyFlag){if(mNeedsNewAnimationIndexWhenRun&&PlayState()!=AnimationPlayState::Idle){mAnimationIndex=sNextAnimationIndex++;mNeedsNewAnimationIndexWhenRun=false;}Animation::UpdateTiming(aSeekFlag,aSyncNotifyFlag);}voidCSSTransition::QueueEvents(StickyTimeDurationaActiveTime){if(!mOwningElement.IsSet()){return;}dom::Element*owningElement;CSSPseudoElementTypeowningPseudoType;mOwningElement.GetElement(owningElement,owningPseudoType);MOZ_ASSERT(owningElement,"Owning element should be set");nsPresContext*presContext=nsContentUtils::GetContextForContent(owningElement);if(!presContext){return;}constStickyTimeDurationzeroDuration=StickyTimeDuration();TransitionPhasecurrentPhase;StickyTimeDurationintervalStartTime;StickyTimeDurationintervalEndTime;if(!mEffect){currentPhase=GetAnimationPhaseWithoutEffect<TransitionPhase>(*this);}else{ComputedTimingcomputedTiming=mEffect->GetComputedTiming();currentPhase=static_cast<TransitionPhase>(computedTiming.mPhase);intervalStartTime=std::max(std::min(StickyTimeDuration(-mEffect->SpecifiedTiming().Delay()),computedTiming.mActiveDuration),zeroDuration);intervalEndTime=std::max(std::min((EffectEnd()-mEffect->SpecifiedTiming().Delay()),computedTiming.mActiveDuration),zeroDuration);}// TimeStamps to use for ordering the events when they are dispatched. We// use a TimeStamp so we can compare events produced by different elements,// perhaps even with different timelines.// The zero timestamp is for transitionrun events where we ignore the delay// for the purpose of ordering events.TimeStampzeroTimeStamp=AnimationTimeToTimeStamp(zeroDuration);TimeStampstartTimeStamp=ElapsedTimeToTimeStamp(intervalStartTime);TimeStampendTimeStamp=ElapsedTimeToTimeStamp(intervalEndTime);if(mPendingState!=PendingState::NotPending&&(mPreviousTransitionPhase==TransitionPhase::Idle||mPreviousTransitionPhase==TransitionPhase::Pending)){currentPhase=TransitionPhase::Pending;}AutoTArray<TransitionEventParams,3>events;// Handle cancel events firstif((mPreviousTransitionPhase!=TransitionPhase::Idle&&mPreviousTransitionPhase!=TransitionPhase::After)&¤tPhase==TransitionPhase::Idle){TimeStampactiveTimeStamp=ElapsedTimeToTimeStamp(aActiveTime);events.AppendElement(TransitionEventParams{eTransitionCancel,aActiveTime,activeTimeStamp});}// All other eventsswitch(mPreviousTransitionPhase){caseTransitionPhase::Idle:if(currentPhase==TransitionPhase::Pending||currentPhase==TransitionPhase::Before){events.AppendElement(TransitionEventParams{eTransitionRun,intervalStartTime,zeroTimeStamp});}elseif(currentPhase==TransitionPhase::Active){events.AppendElement(TransitionEventParams{eTransitionRun,intervalStartTime,zeroTimeStamp});events.AppendElement(TransitionEventParams{eTransitionStart,intervalStartTime,startTimeStamp});}elseif(currentPhase==TransitionPhase::After){events.AppendElement(TransitionEventParams{eTransitionRun,intervalStartTime,zeroTimeStamp});events.AppendElement(TransitionEventParams{eTransitionStart,intervalStartTime,startTimeStamp});events.AppendElement(TransitionEventParams{eTransitionEnd,intervalEndTime,endTimeStamp});}break;caseTransitionPhase::Pending:caseTransitionPhase::Before:if(currentPhase==TransitionPhase::Active){events.AppendElement(TransitionEventParams{eTransitionStart,intervalStartTime,startTimeStamp});}elseif(currentPhase==TransitionPhase::After){events.AppendElement(TransitionEventParams{eTransitionStart,intervalStartTime,startTimeStamp});events.AppendElement(TransitionEventParams{eTransitionEnd,intervalEndTime,endTimeStamp});}break;caseTransitionPhase::Active:if(currentPhase==TransitionPhase::After){events.AppendElement(TransitionEventParams{eTransitionEnd,intervalEndTime,endTimeStamp});}elseif(currentPhase==TransitionPhase::Before){events.AppendElement(TransitionEventParams{eTransitionEnd,intervalStartTime,startTimeStamp});}break;caseTransitionPhase::After:if(currentPhase==TransitionPhase::Active){events.AppendElement(TransitionEventParams{eTransitionStart,intervalEndTime,startTimeStamp});}elseif(currentPhase==TransitionPhase::Before){events.AppendElement(TransitionEventParams{eTransitionStart,intervalEndTime,startTimeStamp});events.AppendElement(TransitionEventParams{eTransitionEnd,intervalStartTime,endTimeStamp});}break;}mPreviousTransitionPhase=currentPhase;nsTransitionManager*manager=presContext->TransitionManager();for(constTransitionEventParams&evt:events){manager->QueueEvent(TransitionEventInfo(owningElement,owningPseudoType,evt.mMessage,TransitionProperty(),evt.mElapsedTime,evt.mTimeStamp,this));}}voidCSSTransition::Tick(){Animation::Tick();QueueEvents();}nsCSSPropertyIDCSSTransition::TransitionProperty()const{MOZ_ASSERT(eCSSProperty_UNKNOWN!=mTransitionProperty,"Transition property should be initialized");returnmTransitionProperty;}AnimationValueCSSTransition::ToValue()const{MOZ_ASSERT(!mTransitionToValue.IsNull(),"Transition ToValue should be initialized");returnmTransitionToValue;}boolCSSTransition::HasLowerCompositeOrderThan(constCSSTransition&aOther)const{MOZ_ASSERT(IsTiedToMarkup()&&aOther.IsTiedToMarkup(),"Should only be called for CSS transitions that are sorted ""as CSS transitions (i.e. tied to CSS markup)");// 0. Object-equality caseif(&aOther==this){returnfalse;}// 1. Sort by document orderif(!mOwningElement.Equals(aOther.mOwningElement)){returnmOwningElement.LessThan(aOther.mOwningElement);}// 2. (Same element and pseudo): Sort by transition generationif(mAnimationIndex!=aOther.mAnimationIndex){returnmAnimationIndex<aOther.mAnimationIndex;}// 3. (Same transition generation): Sort by transition propertyreturnnsCSSProps::GetStringValue(TransitionProperty())<nsCSSProps::GetStringValue(aOther.TransitionProperty());}/* static */Nullable<TimeDuration>CSSTransition::GetCurrentTimeAt(constdom::DocumentTimeline&aTimeline,constTimeStamp&aBaseTime,constTimeDuration&aStartTime,doubleaPlaybackRate){Nullable<TimeDuration>result;Nullable<TimeDuration>timelineTime=aTimeline.ToTimelineTime(aBaseTime);if(!timelineTime.IsNull()){result.SetValue((timelineTime.Value()-aStartTime).MultDouble(aPlaybackRate));}returnresult;}voidCSSTransition::SetEffectFromStyle(dom::AnimationEffectReadOnly*aEffect){Animation::SetEffectNoUpdate(aEffect);// Initialize transition property.ElementPropertyTransition*pt=aEffect?aEffect->AsTransition():nullptr;if(eCSSProperty_UNKNOWN==mTransitionProperty&&pt){mTransitionProperty=pt->TransitionProperty();mTransitionToValue=pt->ToValue();}}////////////////////////// nsTransitionManager ////////////////////////////NS_IMPL_CYCLE_COLLECTION(nsTransitionManager,mEventDispatcher)NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsTransitionManager,AddRef)NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsTransitionManager,Release)staticinlineboolExtractNonDiscreteComputedValue(nsCSSPropertyIDaProperty,nsStyleContext*aStyleContext,AnimationValue&aAnimationValue){return(nsCSSProps::kAnimTypeTable[aProperty]!=eStyleAnimType_Discrete||aProperty==eCSSProperty_visibility)&&StyleAnimationValue::ExtractComputedValue(aProperty,aStyleContext,aAnimationValue.mGecko);}staticinlineboolExtractNonDiscreteComputedValue(nsCSSPropertyIDaProperty,constServoComputedValues*aComputedStyle,AnimationValue&aAnimationValue){if(Servo_Property_IsDiscreteAnimatable(aProperty)&&aProperty!=eCSSProperty_visibility){returnfalse;}aAnimationValue.mServo=Servo_ComputedValues_ExtractAnimationValue(aComputedStyle,aProperty).Consume();return!!aAnimationValue.mServo;}voidnsTransitionManager::StyleContextChanged(dom::Element*aElement,nsStyleContext*aOldStyleContext,RefPtr<nsStyleContext>*aNewStyleContext/* inout */){nsStyleContext*newStyleContext=*aNewStyleContext;NS_PRECONDITION(aOldStyleContext->GetPseudo()==newStyleContext->GetPseudo(),"pseudo type mismatch");if(mInAnimationOnlyStyleUpdate){// If we're doing an animation-only style update, return, since the// purpose of an animation-only style update is to update only the// animation styles so that we don't consider style changes// resulting from changes in the animation time for starting a// transition.return;}if(!mPresContext->IsDynamic()){// For print or print preview, ignore transitions.return;}if(aOldStyleContext->HasPseudoElementData()!=newStyleContext->HasPseudoElementData()){// If the old style context and new style context differ in terms of// whether they're inside ::first-letter, ::first-line, or similar,// bail. We can't hit this codepath for normal style changes// involving moving frames around the boundaries of these// pseudo-elements since we don't call StyleContextChanged from// ReparentStyleContext. However, we can hit this codepath during// the handling of transitions that start across reframes.//// While there isn't an easy *perfect* way to handle this case, err// on the side of missing some transitions that we ought to have// rather than having bogus transitions that we shouldn't.//// We could consider changing this handling, although it's worth// thinking about whether the code below could do anything weird in// this case.return;}// NOTE: Things in this function (and ConsiderInitiatingTransition)// should never call PeekStyleData because we don't preserve gotten// structs across reframes.// Return sooner (before the startedAny check below) for the most// common case: no transitions specified or running.constnsStyleDisplay*disp=newStyleContext->StyleDisplay();CSSPseudoElementTypepseudoType=newStyleContext->GetPseudoType();if(pseudoType!=CSSPseudoElementType::NotPseudo){if(pseudoType!=CSSPseudoElementType::before&&pseudoType!=CSSPseudoElementType::after){return;}NS_ASSERTION((pseudoType==CSSPseudoElementType::before&&aElement->IsGeneratedContentContainerForBefore())||(pseudoType==CSSPseudoElementType::after&&aElement->IsGeneratedContentContainerForAfter()),"Unexpected aElement coming through");// Else the element we want to use from now on is the element the// :before or :after is attached to.aElement=aElement->GetParent()->AsElement();}CSSTransitionCollection*collection=CSSTransitionCollection::GetAnimationCollection(aElement,pseudoType);if(!collection&&disp->mTransitionPropertyCount==1&&disp->mTransitions[0].GetCombinedDuration()<=0.0f){return;}MOZ_ASSERT(mPresContext->RestyleManager()->IsGecko(),"ServoRestyleManager should not use nsTransitionManager ""for transitions");if(collection&&collection->mCheckGeneration==mPresContext->RestyleManager()->GetAnimationGeneration()){// When we start a new transition, we immediately post a restyle.// If the animation generation on the collection is current, that// means *this* is that restyle, since we bump the animation// generation on the restyle manager whenever there's a real style// change (i.e., one where mInAnimationOnlyStyleUpdate isn't true,// which causes us to return above). Thus we shouldn't do anything.return;}if(newStyleContext->GetParent()&&newStyleContext->GetParent()->HasPseudoElementData()){// Ignore transitions on things that inherit properties from// pseudo-elements.// FIXME (Bug 522599): Add tests for this.return;}NS_WARNING_ASSERTION(!mPresContext->EffectCompositor()->HasThrottledStyleUpdates(),"throttled animations not up to date");// Compute what the css-transitions spec calls the "after-change// style", which is the new style without any data from transitions,// but still inheriting from data that contains transitions that are// not stopping or starting right now.RefPtr<nsStyleContext>afterChangeStyle;if(collection){MOZ_ASSERT(mPresContext->StyleSet()->IsGecko(),"ServoStyleSets should not use nsTransitionManager ""for transitions");nsStyleSet*styleSet=mPresContext->StyleSet()->AsGecko();afterChangeStyle=styleSet->ResolveStyleByRemovingAnimation(aElement,newStyleContext,eRestyle_CSSTransitions);}else{afterChangeStyle=newStyleContext;}nsAutoAnimationMutationBatchmb(aElement->OwnerDoc());DebugOnly<bool>startedAny=false;// We don't have to update transitions if display:none, although we will// cancel them after restyling.if(!afterChangeStyle->IsInDisplayNoneSubtree()){startedAny=DoUpdateTransitions(disp,aElement,afterChangeStyle->GetPseudoType(),collection,aOldStyleContext,afterChangeStyle.get());}MOZ_ASSERT(!startedAny||collection,"must have element transitions if we started any transitions");EffectCompositor::CascadeLevelcascadeLevel=EffectCompositor::CascadeLevel::Transitions;if(collection){collection->UpdateCheckGeneration(mPresContext);mPresContext->EffectCompositor()->MaybeUpdateAnimationRule(aElement,pseudoType,cascadeLevel,newStyleContext);}// We want to replace the new style context with the after-change style.*aNewStyleContext=afterChangeStyle;if(collection){// Since we have transition styles, we have to undo this replacement.// The check of collection->mCheckGeneration against the restyle// manager's GetAnimationGeneration() will ensure that we don't go// through the rest of this function again when we do.mPresContext->EffectCompositor()->PostRestyleForAnimation(aElement,pseudoType,cascadeLevel);}}boolnsTransitionManager::UpdateTransitions(dom::Element*aElement,CSSPseudoElementTypeaPseudoType,constServoComputedValues*aOldStyle,constServoComputedValues*aNewStyle){if(!mPresContext->IsDynamic()){// For print or print preview, ignore transitions.returnfalse;}CSSTransitionCollection*collection=CSSTransitionCollection::GetAnimationCollection(aElement,aPseudoType);constnsStyleDisplay*disp=Servo_GetStyleDisplay(aNewStyle);returnDoUpdateTransitions(disp,aElement,aPseudoType,collection,aOldStyle,aNewStyle);}template<typenameStyleType>boolnsTransitionManager::DoUpdateTransitions(constnsStyleDisplay*aDisp,dom::Element*aElement,CSSPseudoElementTypeaPseudoType,CSSTransitionCollection*&aElementTransitions,StyleTypeaOldStyle,StyleTypeaNewStyle){MOZ_ASSERT(aDisp,"Null nsStyleDisplay");MOZ_ASSERT(!aElementTransitions||aElementTransitions->mElement==aElement,"Element mismatch");// Per http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html// I'll consider only the transitions from the number of items in// 'transition-property' on down, and later ones will override earlier// ones (tracked using |whichStarted|).boolstartedAny=false;nsCSSPropertyIDSetwhichStarted;for(uint32_ti=aDisp->mTransitionPropertyCount;i--!=0;){constStyleTransition&t=aDisp->mTransitions[i];// Check the combined duration (combination of delay and duration)// first, since it defaults to zero, which means we can ignore the// transition.if(t.GetCombinedDuration()>0.0f){// We might have something to transition. See if any of the// properties in question changed and are animatable.// FIXME: Would be good to find a way to share code between this// interpretation of transition-property and the one below.nsCSSPropertyIDproperty=t.GetProperty();if(property==eCSSPropertyExtra_no_properties||property==eCSSPropertyExtra_variable||property==eCSSProperty_UNKNOWN){// Nothing to do, but need to exclude this from cases below.}elseif(property==eCSSPropertyExtra_all_properties){for(nsCSSPropertyIDp=nsCSSPropertyID(0);p<eCSSProperty_COUNT_no_shorthands;p=nsCSSPropertyID(p+1)){ConsiderInitiatingTransition(p,t,aElement,aPseudoType,aElementTransitions,aOldStyle,aNewStyle,&startedAny,&whichStarted);}}elseif(nsCSSProps::IsShorthand(property)){CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop,property,CSSEnabledState::eForAllContent){ConsiderInitiatingTransition(*subprop,t,aElement,aPseudoType,aElementTransitions,aOldStyle,aNewStyle,&startedAny,&whichStarted);}}else{ConsiderInitiatingTransition(property,t,aElement,aPseudoType,aElementTransitions,aOldStyle,aNewStyle,&startedAny,&whichStarted);}}}// Stop any transitions for properties that are no longer in// 'transition-property', including finished transitions.// Also stop any transitions (and remove any finished transitions)// for properties that just changed (and are still in the set of// properties to transition), but for which we didn't just start the// transition. This can happen delay and duration are both zero, or// because the new value is not interpolable.// Note that we also do the latter set of work in// nsTransitionManager::PruneCompletedTransitions.if(aElementTransitions){boolcheckProperties=aDisp->mTransitions[0].GetProperty()!=eCSSPropertyExtra_all_properties;nsCSSPropertyIDSetallTransitionProperties;if(checkProperties){for(uint32_ti=aDisp->mTransitionPropertyCount;i--!=0;){constStyleTransition&t=aDisp->mTransitions[i];// FIXME: Would be good to find a way to share code between this// interpretation of transition-property and the one above.nsCSSPropertyIDproperty=t.GetProperty();if(property==eCSSPropertyExtra_no_properties||property==eCSSPropertyExtra_variable||property==eCSSProperty_UNKNOWN){// Nothing to do, but need to exclude this from cases below.}elseif(property==eCSSPropertyExtra_all_properties){for(nsCSSPropertyIDp=nsCSSPropertyID(0);p<eCSSProperty_COUNT_no_shorthands;p=nsCSSPropertyID(p+1)){allTransitionProperties.AddProperty(p);}}elseif(nsCSSProps::IsShorthand(property)){CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop,property,CSSEnabledState::eForAllContent){allTransitionProperties.AddProperty(*subprop);}}else{allTransitionProperties.AddProperty(property);}}}OwningCSSTransitionPtrArray&animations=aElementTransitions->mAnimations;size_ti=animations.Length();MOZ_ASSERT(i!=0,"empty transitions list?");AnimationValuecurrentValue;do{--i;CSSTransition*anim=animations[i];// properties no longer in 'transition-property'if((checkProperties&&!allTransitionProperties.HasProperty(anim->TransitionProperty()))||// properties whose computed values changed but for which we// did not start a new transition (because delay and// duration are both zero, or because the new value is not// interpolable); a new transition would have anim->ToValue()// matching currentValue!ExtractNonDiscreteComputedValue(anim->TransitionProperty(),aNewStyle,currentValue)||currentValue!=anim->ToValue()){// stop the transitionif(anim->HasCurrentEffect()){EffectSet*effectSet=EffectSet::GetEffectSet(aElement,aPseudoType);if(effectSet){effectSet->UpdateAnimationGeneration(mPresContext);}}anim->CancelFromStyle();animations.RemoveElementAt(i);}}while(i!=0);if(animations.IsEmpty()){aElementTransitions->Destroy();aElementTransitions=nullptr;}}returnstartedAny;}staticKeyframe&AppendKeyframe(doubleaOffset,nsCSSPropertyIDaProperty,AnimationValue&&aValue,nsTArray<Keyframe>&aKeyframes){Keyframe&frame=*aKeyframes.AppendElement();frame.mOffset.emplace(aOffset);if(aValue.mServo){RefPtr<RawServoDeclarationBlock>decl=Servo_AnimationValue_Uncompute(aValue.mServo).Consume();frame.mPropertyValues.AppendElement(Move(PropertyValuePair(aProperty,Move(decl))));}else{nsCSSValuepropertyValue;DebugOnly<bool>uncomputeResult=StyleAnimationValue::UncomputeValue(aProperty,Move(aValue.mGecko),propertyValue);MOZ_ASSERT(uncomputeResult,"Unable to get specified value from computed value");frame.mPropertyValues.AppendElement(Move(PropertyValuePair(aProperty,Move(propertyValue))));}returnframe;}staticnsTArray<Keyframe>GetTransitionKeyframes(nsCSSPropertyIDaProperty,AnimationValue&&aStartValue,AnimationValue&&aEndValue,constnsTimingFunction&aTimingFunction){nsTArray<Keyframe>keyframes(2);Keyframe&fromFrame=AppendKeyframe(0.0,aProperty,Move(aStartValue),keyframes);if(aTimingFunction.mType!=nsTimingFunction::Type::Linear){fromFrame.mTimingFunction.emplace();fromFrame.mTimingFunction->Init(aTimingFunction);}AppendKeyframe(1.0,aProperty,Move(aEndValue),keyframes);returnkeyframes;}staticboolIsTransitionable(nsCSSPropertyIDaProperty,boolaIsServo){if(aIsServo){returnServo_Property_IsTransitionable(aProperty);}// FIXME: This should also exclude discretely-animated properties.returnnsCSSProps::kAnimTypeTable[aProperty]!=eStyleAnimType_None;}template<typenameStyleType>voidnsTransitionManager::ConsiderInitiatingTransition(nsCSSPropertyIDaProperty,constStyleTransition&aTransition,dom::Element*aElement,CSSPseudoElementTypeaPseudoType,CSSTransitionCollection*&aElementTransitions,StyleTypeaOldStyle,StyleTypeaNewStyle,bool*aStartedAny,nsCSSPropertyIDSet*aWhichStarted){// IsShorthand itself will assert if aProperty is not a property.MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty),"property out of range");NS_ASSERTION(!aElementTransitions||aElementTransitions->mElement==aElement,"Element mismatch");// Ignore disabled properties. We can arrive here if the transition-property// is 'all' and the disabled property has a default value which derives value// from another property, e.g. color.if(!nsCSSProps::IsEnabled(aProperty,CSSEnabledState::eForAllContent)){return;}if(aWhichStarted->HasProperty(aProperty)){// A later item in transition-property already started a// transition for this property, so we ignore this one.// See comment above and// http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html .return;}if(!IsTransitionable(aProperty,aElement->IsStyledByServo())){return;}dom::DocumentTimeline*timeline=aElement->OwnerDoc()->Timeline();AnimationValuestartValue,endValue;boolhaveValues=ExtractNonDiscreteComputedValue(aProperty,aOldStyle,startValue)&&ExtractNonDiscreteComputedValue(aProperty,aNewStyle,endValue);boolhaveChange=startValue!=endValue;boolshouldAnimate=haveValues&&haveChange&&startValue.IsInterpolableWith(aProperty,endValue);boolhaveCurrentTransition=false;size_tcurrentIndex=nsTArray<ElementPropertyTransition>::NoIndex;constElementPropertyTransition*oldPT=nullptr;if(aElementTransitions){OwningCSSTransitionPtrArray&animations=aElementTransitions->mAnimations;for(size_ti=0,i_end=animations.Length();i<i_end;++i){if(animations[i]->TransitionProperty()==aProperty){haveCurrentTransition=true;currentIndex=i;oldPT=animations[i]->GetEffect()?animations[i]->GetEffect()->AsTransition():nullptr;break;}}}// If we got a style change that changed the value to the endpoint// of the currently running transition, we don't want to interrupt// its timing function.// This needs to be before the !shouldAnimate && haveCurrentTransition// case below because we might be close enough to the end of the// transition that the current value rounds to the final value. In// this case, we'll end up with shouldAnimate as false (because// there's no value change), but we need to return early here rather// than cancel the running transition because shouldAnimate is false!//// Likewise, if we got a style change that changed the value to the// endpoint of our finished transition, we also don't want to start// a new transition for the reasons described in// https://lists.w3.org/Archives/Public/www-style/2015Jan/0444.html .if(haveCurrentTransition&&haveValues&&aElementTransitions->mAnimations[currentIndex]->ToValue()==endValue){// GetAnimationRule already called RestyleForAnimation.return;}if(!shouldAnimate){if(haveCurrentTransition){// We're in the middle of a transition, and just got a non-transition// style change to something that we can't animate. This might happen// because we got a non-transition style change changing to the current// in-progress value (which is particularly easy to cause when we're// currently in the 'transition-delay'). It also might happen because we// just got a style change to a value that can't be interpolated.OwningCSSTransitionPtrArray&animations=aElementTransitions->mAnimations;animations[currentIndex]->CancelFromStyle();oldPT=nullptr;// Clear pointer so it doesn't dangleanimations.RemoveElementAt(currentIndex);EffectSet*effectSet=EffectSet::GetEffectSet(aElement,aPseudoType);if(effectSet){effectSet->UpdateAnimationGeneration(mPresContext);}if(animations.IsEmpty()){aElementTransitions->Destroy();// |aElementTransitions| is now a dangling pointer!aElementTransitions=nullptr;}// GetAnimationRule already called RestyleForAnimation.}return;}constnsTimingFunction&tf=aTransition.GetTimingFunction();floatdelay=aTransition.GetDelay();floatduration=aTransition.GetDuration();if(duration<0.0){// The spec says a negative duration is treated as zero.duration=0.0;}AnimationValuestartForReversingTest=startValue;doublereversePortion=1.0;// If the new transition reverses an existing one, we'll need to// handle the timing differently.// FIXME: Move mStartForReversingTest, mReversePortion to CSSTransition,// and set the timing function on transitions as an effect-level// easing (rather than keyframe-level easing). (Bug 1292001)if(haveCurrentTransition&&aElementTransitions->mAnimations[currentIndex]->HasCurrentEffect()&&oldPT&&oldPT->mStartForReversingTest==endValue){// Compute the appropriate negative transition-delay such that right// now we'd end up at the current position.doublevaluePortion=oldPT->CurrentValuePortion()*oldPT->mReversePortion+(1.0-oldPT->mReversePortion);// A timing function with negative y1 (or y2!) might make// valuePortion negative. In this case, we still want to apply our// reversing logic based on relative distances, not make duration// negative.if(valuePortion<0.0){valuePortion=-valuePortion;}// A timing function with y2 (or y1!) greater than one might// advance past its terminal value. It's probably a good idea to// clamp valuePortion to be at most one to preserve the invariant// that a transition will complete within at most its specified// time.if(valuePortion>1.0){valuePortion=1.0;}// Negative delays are essentially part of the transition// function, so reduce them along with the duration, but don't// reduce positive delays.if(delay<0.0f){delay*=valuePortion;}duration*=valuePortion;startForReversingTest=oldPT->ToValue();reversePortion=valuePortion;}TimingParamstiming=TimingParamsFromCSSParams(duration,delay,1.0/* iteration count */,dom::PlaybackDirection::Normal,dom::FillMode::Backwards);// aElement is non-null here, so we emplace it directly.Maybe<OwningAnimationTarget>target;target.emplace(aElement,aPseudoType);KeyframeEffectParamseffectOptions;RefPtr<ElementPropertyTransition>pt=newElementPropertyTransition(aElement->OwnerDoc(),target,timing,startForReversingTest,reversePortion,effectOptions);pt->SetKeyframes(GetTransitionKeyframes(aProperty,Move(startValue),Move(endValue),tf),aNewStyle);RefPtr<CSSTransition>animation=newCSSTransition(mPresContext->Document()->GetScopeObject());animation->SetOwningElement(OwningElementRef(*aElement,aPseudoType));animation->SetTimelineNoUpdate(timeline);animation->SetCreationSequence(mPresContext->RestyleManager()->GetAnimationGeneration());animation->SetEffectFromStyle(pt);animation->PlayFromStyle();if(!aElementTransitions){boolcreatedCollection=false;aElementTransitions=CSSTransitionCollection::GetOrCreateAnimationCollection(aElement,aPseudoType,&createdCollection);if(!aElementTransitions){MOZ_ASSERT(!createdCollection,"outparam should agree with return value");NS_WARNING("allocating collection failed");return;}if(createdCollection){AddElementCollection(aElementTransitions);}}OwningCSSTransitionPtrArray&animations=aElementTransitions->mAnimations;#ifdef DEBUGfor(size_ti=0,i_end=animations.Length();i<i_end;++i){MOZ_ASSERT(i==currentIndex||animations[i]->TransitionProperty()!=aProperty,"duplicate transitions for property");}#endifif(haveCurrentTransition){// If this new transition is replacing an existing transition that is running// on the compositor, we store select parameters from the replaced transition// so that later, once all scripts have run, we can update the start value// of the transition using TimeStamp::Now(). This allows us to avoid a// large jump when starting a new transition when the main thread lags behind// the compositor.if(oldPT&&oldPT->IsCurrent()&&oldPT->IsRunningOnCompositor()&&!oldPT->GetAnimation()->GetStartTime().IsNull()&&timeline==oldPT->GetAnimation()->GetTimeline()){constAnimationPropertySegment&segment=oldPT->Properties()[0].mSegments[0];pt->mReplacedTransition.emplace(ElementPropertyTransition::ReplacedTransitionProperties({oldPT->GetAnimation()->GetStartTime().Value(),oldPT->GetAnimation()->PlaybackRate(),oldPT->SpecifiedTiming(),segment.mTimingFunction,segment.mFromValue,segment.mToValue}));}animations[currentIndex]->CancelFromStyle();oldPT=nullptr;// Clear pointer so it doesn't dangleanimations[currentIndex]=animation;}else{if(!animations.AppendElement(animation)){NS_WARNING("out of memory");return;}}EffectSet*effectSet=EffectSet::GetEffectSet(aElement,aPseudoType);if(effectSet){effectSet->UpdateAnimationGeneration(mPresContext);}*aStartedAny=true;aWhichStarted->AddProperty(aProperty);}voidnsTransitionManager::PruneCompletedTransitions(mozilla::dom::Element*aElement,CSSPseudoElementTypeaPseudoType,nsStyleContext*aNewStyleContext){MOZ_ASSERT(!aElement->IsGeneratedContentContainerForBefore()&&!aElement->IsGeneratedContentContainerForAfter());CSSTransitionCollection*collection=CSSTransitionCollection::GetAnimationCollection(aElement,aPseudoType);if(!collection){return;}// Remove any finished transitions whose style doesn't match the new// style.// This is similar to some of the work that happens near the end of// nsTransitionManager::StyleContextChanged.// FIXME (bug 1158431): Really, we should also cancel running// transitions whose destination doesn't match as well.OwningCSSTransitionPtrArray&animations=collection->mAnimations;size_ti=animations.Length();MOZ_ASSERT(i!=0,"empty transitions list?");do{--i;CSSTransition*anim=animations[i];if(anim->HasCurrentEffect()){continue;}// Since effect is a finished transition, we know it didn't// influence style.AnimationValuecurrentValue;if(!ExtractNonDiscreteComputedValue(anim->TransitionProperty(),aNewStyleContext,currentValue)||currentValue!=anim->ToValue()){anim->CancelFromStyle();animations.RemoveElementAt(i);}}while(i!=0);if(collection->mAnimations.IsEmpty()){collection->Destroy();// |collection| is now a dangling pointer!collection=nullptr;}}